/*
 * Licensed this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 * AUTHOR: Sebastian Ritter
 */
using System;
using System.Collections.Generic;
using System.Text;

namespace org.boapi.shop {
  /// <summary>
  /// International Standard Book Number [ISBN] is a 13 digit number since 01.01.2007. Before 01.01.2007
  /// the ISBN has 10 digits.  
  /// </summary>
  /// <version>2.0.0.0</version>
  public abstract class ISBN : org.boapi.core.IChecknumber {

    /// <summary>
    /// Create an old ISBN from new ISBN
    /// </summary>
    /// <param name="isbn13"></param>
    /// <returns></returns>
    public static ISBN10 parse (ISBN13 isbn13) {
      ISBN10 isbn = new ISBN10 ();
      isbn.setCompleteValue (isbn13.getNumber ().Substring(3,10));
      isbn.setCompleteValue (isbn.getNumber ().Substring (0, 9) + isbn.getChecknumber ());
      return isbn;
    }
    /// <summary>
    /// Create a new ISBN from EAN or GTIN code
    /// </summary>
    /// <param name="gtin"></param>
    /// <returns></returns>
    public static ISBN13 parse (GTIN gtin) {
      ISBN13 isbn = new ISBN13 ();
      isbn.setCompleteValue (gtin.getNumber ());
      return isbn;
    }
    /// <summary>
    /// The check digit
    /// </summary>
    private char checknumber = 'R';
    /// <summary>
    /// Get the check digit
    /// </summary>
    /// <returns></returns>
    protected virtual char getChecknumber () {
      if ('R' == this.checknumber) {
        this.isChecknumberCorrect ();
      }
      return this.checknumber;
    }
    /// <summary>
    /// Set the check digit
    /// </summary>
    /// <param name="digit"></param>
    protected virtual void setChecknumber (char digit) {
      this.checknumber = digit;
    }
    /// <summary>
    /// International Standard Book Number
    /// </summary>
    private String number;
    /// <summary>
    /// Set the ISBN
    /// </summary>
    /// <param name="isbn"></param>
    public virtual void setCompleteValue (params string[] isbn) {
      StringBuilder nummer = new StringBuilder ();
      foreach (Char c in isbn[0].ToUpper().ToCharArray ()) {
        if (Char.IsDigit (c) || c == 'X') {
          nummer.Append (c);
        }
        else if (c != '-') {
          throw new ArgumentException ("Illegal character in ISBN", isbn[0]);
        }
      }
      this.setNumber(nummer.ToString ());
      this.setChecknumber ('R');
    }
    /// <summary>
    /// The number of ISBN
    /// </summary>
    /// <returns></returns>
    public virtual String getNumber () {
      return this.number;
    }
    /// <summary>
    /// Set the ISBN number
    /// </summary>
    /// <param name="number"></param>
    protected void setNumber (String number) {
      this.number = number;
    }
    /// <summary>
    /// Is the checknumber correct
    /// </summary>
    /// <returns></returns>
    public abstract bool isChecknumberCorrect ();
  }
  /// <summary>
  /// International Standard Book Number [ISBN] before 01.01.2007.  
  /// </summary>
  public sealed class ISBN10 : ISBN {
    /// <summary>
    /// Is the checknumber correct
    /// </summary>
    /// <returns></returns>
    public override sealed bool isChecknumberCorrect () {
      int[] factors = new int[] { 10,9,8, 7, 6, 5, 4, 3, 2 }; // only first seven
      int[] digits = new int[factors.Length];
      for (int i = 0; i < digits.Length; i++) {
        digits[i] = int.Parse (this.getNumber().Substring (i, 1));
      }
      int sum = 0;
      for (int i = 0; i < digits.Length; i++) {
        sum += digits[i] * factors[i];
      }
      int mod11 = sum % 11;
      int checkDigit = 11 - mod11;
      switch (checkDigit) {
        case 10:
          this.setChecknumber('X');
          break;
        case 11:
          this.setChecknumber('0');
          break;
        default:
          this.setChecknumber(char.Parse (checkDigit.ToString ()));
          break;
      }
      return this.getChecknumber() == this.getNumber().ToCharArray (9, 1)[0];
    }
  }
  /// <summary>
  /// International Standard Book Number [ISBN] since 01.01.2007.
  /// </summary>
  public sealed class ISBN13 : ISBN {
     
    /// <summary>
    /// Delegate object
    /// </summary>
    private GTIN delegateObject;
    /// <summary>
    /// Construct
    /// </summary>
    public ISBN13 () {
      this.delegateObject = new GTIN ();
    }
    /// <summary>
    /// Set the ISBN
    /// </summary>
    /// <param name="isbn"></param>
    public override void setCompleteValue (params string[] isbn) {
      this.delegateObject.setCompleteValue (isbn[0]);
      if (!(this.delegateObject.getNumber().StartsWith ("978") 
           || this.delegateObject.getNumber().StartsWith ("979"))) {
        throw new ArgumentException ("Number is not an ISBN, because it starts not with 978 or 979", "isbn");
      }
    }

    /// <summary>
    /// Is the checknumber correct
    /// </summary>
    /// <returns></returns>
    public override sealed bool isChecknumberCorrect () {
      return this.delegateObject.isChecknumberCorrect ();
    }
    /// <summary>
    /// Set the checknumber
    /// </summary>
    /// <param name="digit"></param>
    protected override void setChecknumber (char digit) {
      this.delegateObject.setChecknumber (digit);
    }
    /// <summary>
    /// Return the checknumber
    /// </summary>
    /// <returns></returns>
    protected override char getChecknumber () {
      return this.delegateObject.getChecknumber ();
    }
    /// <summary>
    /// The number of ISBN
    /// </summary>
    /// <returns></returns>
    public override String getNumber () {
      return this.delegateObject.getNumber();
    }

  }
}
